#ifndef EM80_PACKET_BASE_HPP
#define EM80_PACKET_BASE_HPP


#include <string>
#include <list>
#include <vector>
#include <utility>

#include <boost/crc.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

#include "../v8stdint.h"        //windows compliation

namespace em80_driver
{

typedef std::vector<uint8_t> Buffer;
typedef std::pair<Buffer::iterator, Buffer::iterator> BufferRange;
typedef std::pair<Buffer::const_iterator, Buffer::const_iterator> BufferRangeConst;

class EM80Packet;
typedef boost::shared_ptr<EM80Packet> EM80PacketPtr;
typedef boost::shared_ptr<EM80Packet const> EM80PacketConstPtr;

class EM80PayloadBase;
typedef boost::shared_ptr<EM80PayloadBase> EM80PayloadBasePtr;
typedef boost::shared_ptr<EM80PayloadBase const> EM80PayloadBaseConstPtr;



/** The raw frame for communicating with the EM80 */
/** It is not intended to be constructed by the users directly */
class EM80Frame
{
public:
    virtual ~EM80Frame(){};

    //getters
    virtual const Buffer& frame() const {return *frame_;}
    virtual const BufferRange& packet() const {return packet_;}
    virtual const Buffer::iterator& payload() const {return payload_;}


    // Frame properties (traits)
    static const int EM80_PAYLOAD_PREFIX = 2;
    static const int EM80_MAX_PAYLOAD_SIZE = 255;  //< Maximum EM80 payload size, in bytes
    static const int EM80_MIN_FRAME_SIZE = 8;       //< Smallest EM80 frame size, in bytes
    static const int EM80_FRAME_HEADTAIL = 5;
    static const int EM80_MAX_FRAME_SIZE = EM80_FRAME_HEADTAIL + EM80_MAX_PAYLOAD_SIZE;   //< Largest EM80 frame size, in bytes
    static const unsigned int EM80_SOF_FRIST_VAL_FRAME = 0xAA;
    static const unsigned int EM80_SOF_SECOND_VAL_FRAME = 0x55;


    /** CRC parameters for the EM80 */
    typedef boost::crc_optimal<16, 0x8005, 0xffff, 0, true, true> CRC;

protected:
    /** Construct frame with specified packet size. */
    EM80Frame(int packet_size);

    boost::shared_ptr<Buffer> frame_;           ///< Stores frame data, shared_ptr for shallow copy

    BufferRange packet_;                       ///< View into frame's packet section
    Buffer::iterator payload_;                 ///< Current read payload footstamp


private:
    /** Construct from buffer. Used by method generateEM80Packet. */
    EM80Frame(const BufferRangeConst &frame, const BufferRangeConst &packet);
    friend boost::shared_ptr<EM80Packet> generateEM80Packet(const Buffer::const_iterator &begin,
                                                            const Buffer::const_iterator &end,
                                                            int *num_bytes_needed, std::string *what);
};


EM80PacketPtr generateEM80Packet(const Buffer::const_iterator &begin,
                                 const Buffer::const_iterator &end,
                                 int *num_bytes_needed, std::string *what);

/** A EM80Packet is a VescFrame with non-zero length payload(s) */
class EM80Packet : public EM80Frame, public boost::enable_shared_from_this<EM80Packet>
{
public:

    virtual ~EM80Packet() {}

    virtual bool serialize();

    virtual bool deserialize();

public:  //no decendant(s) up to now
    EM80Packet(int packet_size);                    //create for request
    EM80Packet(boost::shared_ptr<EM80Frame> raw);  //reflect from the response raw data stream

public:
    virtual void pushPayload(const boost::shared_ptr<EM80PayloadBase> &payload);  // (serial_read) interface --> deserialize --> push

    virtual void popPayload(boost::shared_ptr<EM80PayloadBase> &payload);   // pop --> serialize --> (serial_write)interface


//private:
public:         //TODO: currently it is set to be public since the list is used by the EM80Interface to handle the payload publishment
    std::list<EM80PayloadBasePtr> payloadList_;
};



class EM80PayloadBase
{
public:
    /**
     * this is simple magic to write the flag, when we get the packet from the host or
     * when we want to send the data
     */
    bool yes;

    /**
     * it indicates the type of derived payload.If the packet type is dynamic, length of
     * packet can be changed during communication session. Otherwise can not.
     */
    const bool is_dynamic;

    /**
     * it indicates length of data part of packet, except header and length field.
     * if packet is fixed type, this value should be matched with length filed.
     * if packet is dynamic type, this value indicates minimal value of length field.
     */
    const uint8_t length;


    const std::string name;


    EM80PacketPtr packet;

    /*
     * construct and destruct
     */
    EM80PayloadBase(EM80PacketPtr packet,
                    const std::string &name_  = std::string(),
                    const bool is_dynamic_ = false,
                    const uint8_t length_ = 0) :                   yes(false),
                                                                   is_dynamic(is_dynamic_),
                                                                   length(length_),
                                                                   name(name_),
                                                                   packet(packet)
    {}


    virtual ~EM80PayloadBase(){}

    /*
     * serialization
     */
    virtual bool serialize() = 0;     //TODO: the decendant(s) refering the Buffer::iterator payload_ from the EM80Packet
    virtual bool deserialize() = 0;

    const std::string& payloadName() const { return name;}

    // utilities
    // @TODO: let's put more useful converters here. Or we may use generic programming.
protected:
    //below function should be replaced with converter
    template <typename T>
    void buildVariable(T &V, Buffer &buffer)
    {


        if(buffer.size() < sizeof(T))
            return;

        //TODO: assertion of inherition
        V = static_cast<uint8_t>(*(buffer.begin()));

        buffer.erase(buffer.begin());

        uint32_t size_value(sizeof(T));
        
        //LSB sequence
        for(uint32_t i = 1; i < size_value; i++)
        {
            V |= ((static_cast<uint8_t>(*(buffer.begin()))) << (8 * i));
            buffer.erase(buffer.begin());
        }
    }

    template <typename T>
    void buildBytes(const T &V, Buffer &buffer)
    {
        uint32_t size_value(sizeof(T));

        //LSB sequence
        for(uint32_t i = 0; i < size_value; i++)
            buffer.push_back(static_cast<uint8_t>((V >> (i * 8)) & 0xff));
    }
};




/**
 * Need to be very careful with this - it will only work across platforms if they
 * happen to be doing "reinterpret_cast" with the same float standard.
 * @param V
 * @param buffer
 */
template <>
inline void EM80PayloadBase::buildVariable<float>(float &V, Buffer &buffer)
{
    if(buffer.size() < 4)
        return;

    uint32_t ui;
    ui = static_cast<uint8_t>(*(buffer.begin()));
    buffer.erase(buffer.begin());

    uint32_t size_value(4);

    for(uint32_t i = 1; i < size_value; i++)
    {
        ui |= ((static_cast<uint8_t>(*buffer.begin())) << (8 * i));
        buffer.erase(buffer.begin());
    }

    V = reinterpret_cast<float &>(ui);      //be careful the behavior with different platforms
}

template <>
inline void EM80PayloadBase::buildBytes<float>(const float &V, Buffer &buffer)
{
    uint32_t size_value(4);

    uint32_t ui(reinterpret_cast<const uint32_t&>(V));

    for(uint32_t i = 0; i < size_value; i++)
    {
        buffer.push_back(static_cast<uint8_t>((ui >> (i * 8)) & 0xff));
    }
}

} //namespace em80_driver
#endif /* EM80_PACKET_BASE_HPP */
